import xlrd
import matplotlib.pyplot as plt
from pyproj import Proj
import pyproj
import math

EXCEL_FILE_NAME = './NoFlyZone/禁飞区信息.xls'

# 画图显示中文需要加此两行
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False


class NoFlyZoneExcel(object):
    """禁飞区 Excel 文档类"""

    def __init__(self, *args):
        """禁飞区 Excel 类初始化"""
        if args[0]:
            self.work_book = xlrd.open_workbook(filename=args[0])
            self.work_sheet = self.work_book.sheet_by_name('Sheet1')
            # 数据地理坐标系
            self.p_wgs84 = Proj(init='epsg:4326')
            # 墨卡托网络平面坐标
            self.p_web_mercator = Proj(init='epsg:3857')
            self._get_no_fly_zone_msg()
            self._get_no_fly_zone_lng_lat()
            self._get_no_fly_zone_plane_coordinates()
        else:
            raise ValueError('%s is invalid file name!' % args[0])

    def _get_no_fly_zone_msg(self):
        """从 Excel 中的数据导出数据字典"""
        # 第一行的除了机场名字，其它项作为 key
        no_fly_zone_keys = []
        self.__no_fly_zone_dict = {}
        for i in range(self.work_sheet.nrows):
            row = self.work_sheet.row_values(i)
            if i is 0:
                no_fly_zone_keys = row
            else:
                temp_dict = {}
                for index, key in enumerate(no_fly_zone_keys[1:], 1):
                    temp_dict[key] = row[index]
                self.__no_fly_zone_dict['line ' + str(i + 1)] = temp_dict
        return self.__no_fly_zone_dict

    def __lng_lat_str2int(self, s):
        """读分秒转换为经纬度"""
        s = s.strip('N').split('E')
        if len(s) is 2:
            lat = s[0].strip('N').strip().strip('′').strip('′').split('°')
            lat = float(lat[0]) + float(lat[1]) / 60
            lng = s[1].strip('E').strip().strip('′').strip('′').split('°')
            lng = float(lng[0]) + float(lng[1]) / 60
            # N39°55.6′E116°33.2′
            return lng, lat
        else:
            return ()

    def _get_no_fly_zone_lng_lat(self):
        """经纬度分秒转换为浮点型经纬度数据"""
        self.__no_fly_zone_lng_lat_dict = {}
        for location, value in self.__no_fly_zone_dict.items():
            # 将 A1~A4 B1~B4 C1~C4 的经纬度提出来
            self.__no_fly_zone_lng_lat_dict[location] = [
                value['A1'], value['A2'], value['C2'], value['B2'],
                value['B3'], value['C3'], value['A3'], value['A4'],
                value['C4'], value['B4'], value['B1'], value['C1']
            ]
            # 如果坐标不为空，则进行转换
            if (value['A1'] and value['A2'] and value['A3'] and value['A4']
                    and value['B1'] and value['B2'] and value['B3']
                    and value['B4'] and value['C1'] and value['C2']
                    and value['C3'] and value['C4']):
                # 将字符串形式得经纬度转为浮点数
                for index, lng_lat_str in enumerate(
                        self.__no_fly_zone_lng_lat_dict[location]):
                    self.__no_fly_zone_lng_lat_dict[location][index] = list(
                        self.__lng_lat_str2int(lng_lat_str))
                    # 经纬度后添加高度数值项
                    if value['机场标高(m)']:
                        self.__no_fly_zone_lng_lat_dict[location][
                            index].append(float(value['机场标高(m)']))
                    else:
                        self.__no_fly_zone_lng_lat_dict[location][
                            index].append(0)
            # 如果坐标为空，则将此区域剔除
            else:
                self.__no_fly_zone_lng_lat_dict.pop(location)
        return self.__no_fly_zone_lng_lat_dict

    def _get_no_fly_zone_plane_coordinates(self):
        """获取禁飞区的平面坐标"""
        self.__no_fly_zone_plane_coordinates = {}
        # 将禁飞区域顶点的经纬度转换为平面坐标
        for key, value in self.__no_fly_zone_lng_lat_dict.items():
            self.__no_fly_zone_plane_coordinates[key] = []
            for lng_lat_alt in value:
                self.__no_fly_zone_plane_coordinates[key].append(
                    pyproj.transform(self.p_wgs84, self.p_web_mercator,
                                     lng_lat_alt[0], lng_lat_alt[1],
                                     lng_lat_alt[2]))
        return self.__no_fly_zone_plane_coordinates

    def plot_no_fly_zone_plane_coordinates(self, zoneindex=0, **kwargs):
        """禁飞区坐标画图"""
        if zoneindex in range(len(self.__no_fly_zone_plane_coordinates)):
            count = 0
            for key in self.__no_fly_zone_plane_coordinates.keys():
                if (count == zoneindex):
                    zone_key = key
                    break
                count = count + 1
        else:
            zone_key = next(self.__no_fly_zone_plane_coordinates.keys())

        if 'poly' in kwargs:
            # 生成一张画图
            figure = plt.figure()
            my_axis = figure.add_subplot(111)
            my_axis.set_aspect(1)
            my_axis.set_title(zone_key)

            # 如果要画原始的 12 边形
            if 'dodecagon' in kwargs['poly']:
                data = self.__no_fly_zone_plane_coordinates[zone_key]
                lngs = []
                lats = []
                for coords in data:
                    lngs.append(coords[0])
                    lats.append(coords[1])
                lngs.append(lngs[0])
                lats.append(lats[0])
                my_axis.plot(lngs, lats)

            # 如果要画八边形
            if 'octangon' in kwargs['poly']:
                data = self._generate_limited_aera()[0][zone_key]
                lngs = []
                lats = []
                for coords in data:
                    lngs.append(coords[0])
                    lats.append(coords[1])
                lngs.append(lngs[0])
                lats.append(lats[0])
                my_axis.plot(lngs, lats)

            # 如果要画圆形限高区
            # if 'circle' in kwargs['poly']:
            #     data = self._generate_limited_aera()[1][zone_key]
            #     pcircle = plt.Circle(data[0], data[1])
            #     my_axis.add_artist(pcircle)

            plt.show()

    def __get_plane_coordinates_center(self):
        """计算禁飞区平面坐标中心点"""
        self._plane_coordinates_center = {}
        for location, value in self.__no_fly_zone_plane_coordinates.items():
            self._plane_coordinates_center[location] = (0, 0, 0)
            for coordinates in value:
                self._plane_coordinates_center[location] = tuple(
                    map(lambda x, y: x + y / len(value),
                        self._plane_coordinates_center[location], coordinates))

    def _generate_limited_aera(self):
        """生成八边形禁飞区域和圆形限高区域"""
        self.__get_plane_coordinates_center()
        octangle_no_fly_zone = {}
        circle_no_fly_zone = {}
        for key, value in self.__no_fly_zone_plane_coordinates.items():
            # 八边形禁飞区，取的是禁飞区的远端八个顶点
            octangle_no_fly_zone[key] = [
                i for i in value
                if (value.index(i) in [0, 1, 3, 4, 6, 7, 9, 10])
            ]
            # 中心点的平面 x y 坐标
            center_x, center_y = self._plane_coordinates_center[key][
                0], self._plane_coordinates_center[key][1]

            p0_x, p0_y = self.__no_fly_zone_plane_coordinates[key][0][
                0], self.__no_fly_zone_plane_coordinates[key][0][1]

            e_x, e_y = (center_x - p0_x), (center_y - p0_y)

            circle_radius = 100 + math.sqrt(e_x * e_x + e_y * e_y)
            # 圆形限高区，取得是禁飞区中心为圆心，最远点 + 100 m 为半径的圆形
            circle_no_fly_zone[key] = [
                self._plane_coordinates_center[key], circle_radius
            ]

        return octangle_no_fly_zone, circle_no_fly_zone

    def print_no_fly_zone_coordinates_to_file(self, file_name):
        """把禁飞区信息打印到文件"""

        # 获取八边形禁飞区，圆形限高区的平面坐标信息
        octangle_no_fly_zone, circle_no_fly_zone = self._generate_limited_aera(
        )

        # 将平面坐标转换为经纬度
        octangle_no_fly_zone_wgs84 = {}
        circle_no_fly_zone_wgs84 = {}
        for key, value in octangle_no_fly_zone.items():
            octangle_no_fly_zone_wgs84[key] = []
            for plane_coords in value:
                octangle_no_fly_zone_wgs84[key].append(
                    pyproj.transform(self.p_web_mercator, self.p_wgs84,
                                     plane_coords[0], plane_coords[1],
                                     plane_coords[2]))

        for key, value in circle_no_fly_zone.items():
            circle_no_fly_zone_wgs84[key] = [
                pyproj.transform(self.p_web_mercator, self.p_wgs84,
                                 value[0][0], value[0][1], value[0][2]),
                value[1]
            ]

        with open(
                file_name + ".c", 'w', encoding="utf-8") as fc, open(
                    file_name + ".h", "w", encoding="utf-8") as fh:
            # 写 .h 文件
            s = file_name.split('/')[-1]
            fh.write("#ifndef __%s_H__\n" % s.upper())
            fh.write("#define __%s_H__\n" % s.upper())
            fh.write('\n#define DEFAULT_NO_FLY_ZONE_NUM %d\n' % len(
                self.__no_fly_zone_plane_coordinates))
            fh.write("\nextern int const (*NO_FLY_ZONE_OCTANGON[])[3];\n")
            fh.write("\nextern const int NO_FLY_ZONE_CIRCLE[][3];\n")
            fh.write("#endif\n")
            fh.close()

            # 写 .c 文件
            fc.write('#include "%s.h"\n' % s)
            fc.write("\n// ----下面是 8 边形禁飞区信息----\n")
            fc.write('// 每个元素内容为 顶点经度, 纬度, 高度\n')
            couts = 0
            # 写入八边形的数组
            for key, value in octangle_no_fly_zone_wgs84.items():
                fc.write("// 跑道：" + key + "\n")
                fc.write("const int NO_FLY_ZONE_OCTANGON_%d[%d][%d] = {\n" %
                         (couts, len(value), len(value[0])))
                for index, coords in enumerate(value):
                    if index is not (len(value) - 1):
                        fc.write("    {%d, %d, %d},\n" %
                                 (coords[0] * 10000000, coords[1] * 10000000,
                                  coords[2] * 100))
                    else:
                        fc.write("    {%d, %d, %d}};\n" %
                                 (coords[0] * 10000000, coords[1] * 10000000,
                                  coords[2] * 100))
                couts = couts + 1
            fc.write(
                "\n// 八边形禁飞区域数组指针数组\n// 每个元素指向一个 8 × 3 的禁飞区域数组\n// 每个被指向得数组的每个 3 列得元素信息为经纬高\n"
            )
            fc.write("int const (*NO_FLY_ZONE_OCTANGON[%d])[3] = {\n" % couts)
            for i in range(couts):
                if i is not couts - 1:
                    fc.write("    NO_FLY_ZONE_OCTANGON_%d,\n" % i)
                else:
                    fc.write("    NO_FLY_ZONE_OCTANGON_%d};\n" % i)

            fc.write("\n// ----下面是圆形限高区信息----\n")
            fc.write('// 每个元素的三个值分别是 圆心经度\纬度\半径\n')
            fc.write("const int NO_FLY_ZONE_CIRCLE[%d][3] = {\n" %
                     len(circle_no_fly_zone_wgs84))
            couts = 0
            for key, value in circle_no_fly_zone_wgs84.items():
                fc.write("    // 跑道:%s\n" % key)
                if couts is not len(circle_no_fly_zone_wgs84) - 1:
                    fc.write("    {%d, %d, %d},\n" %
                             (value[0][0] * 10000000, value[0][1] * 10000000,
                              value[1] * 100))
                else:
                    fc.write("    {%d, %d, %d}};\n" %
                             (value[0][0] * 10000000, value[0][1] * 10000000,
                              value[1] * 100))
                couts += 1
            fc.close()


if __name__ == '__main__':
    my_excel_data = NoFlyZoneExcel(EXCEL_FILE_NAME)
    my_excel_data.print_no_fly_zone_coordinates_to_file(
        './NoFlyZone/no_fly_zone')
